//: enumerated/VendingMachine.java
// {Args: VendingMachineInput.txt}
package enumerated;

import static enumerated.Input.ABORT_TRANSACTION;
import static enumerated.Input.CHIPS;
import static enumerated.Input.DIME;
import static enumerated.Input.DOLLAR;
import static enumerated.Input.NICKEL;
import static enumerated.Input.QUARTER;
import static enumerated.Input.SOAP;
import static enumerated.Input.SODA;
import static enumerated.Input.STOP;
import static enumerated.Input.TOOTHPASTE;
import static net.mindview.util.Print.print;

import java.util.EnumMap;
import java.util.Iterator;

import net.mindview.util.Generator;
import net.mindview.util.TextFile;

enum Category {
	MONEY(NICKEL, DIME, QUARTER, DOLLAR), ITEM_SELECTION(TOOTHPASTE, CHIPS,
			SODA, SOAP), QUIT_TRANSACTION(ABORT_TRANSACTION), SHUT_DOWN(STOP);
	private Input[] values;

	Category(Input... types) {
		values = types;
	}

	private static EnumMap<Input, Category> categories = new EnumMap<Input, Category>(
			Input.class);
	static {
		for (Category c : Category.class.getEnumConstants())
			for (Input type : c.values)
				categories.put(type, c);
	}

	public static Category categorize(Input input) {
		return categories.get(input);
	}
}

public class VendingMachine {
	private static State state = State.RESTING;
	private static int amount = 0;
	private static Input selection = null;

	enum StateDuration {
		TRANSIENT
	} // Tagging enum

	enum State {
		RESTING {
			void next(Input input) {
				switch (Category.categorize(input)) {
				case MONEY:
					amount += input.amount();
					state = ADDING_MONEY;
					break;
				case SHUT_DOWN:
					state = TERMINAL;
				default:
				}
			}
		},
		ADDING_MONEY {
			void next(Input input) {
				switch (Category.categorize(input)) {
				case MONEY:
					amount += input.amount();
					break;
				case ITEM_SELECTION:
					selection = input;
					if (amount < selection.amount())
						print("Insufficient money for " + selection);
					else
						state = DISPENSING;
					break;
				case QUIT_TRANSACTION:
					state = GIVING_CHANGE;
					break;
				case SHUT_DOWN:
					state = TERMINAL;
				default:
				}
			}
		},
		DISPENSING(StateDuration.TRANSIENT) {
			void next() {
				print("here is your " + selection);
				amount -= selection.amount();
				state = GIVING_CHANGE;
			}
		},
		GIVING_CHANGE(StateDuration.TRANSIENT) {
			void next() {
				if (amount > 0) {
					print("Your change: " + amount);
					amount = 0;
				}
				state = RESTING;
			}
		},
		TERMINAL {
			void output() {
				print("Halted");
			}
		};
		private boolean isTransient = false;

		State() {
		}

		State(StateDuration trans) {
			isTransient = true;
		}

		void next(Input input) {
			throw new RuntimeException("Only call "
					+ "next(Input input) for non-transient states");
		}

		void next() {
			throw new RuntimeException("Only call next() for "
					+ "StateDuration.TRANSIENT states");
		}

		void output() {
			print(amount);
		}
	}

	static void run(Generator<Input> gen) {
		while (state != State.TERMINAL) {
			state.next(gen.next());
			while (state.isTransient)
				state.next();
			state.output();
		}
	}

	public static void main(String[] args) {
		Generator<Input> gen = new FileInputGenerator(
				"bin\\enumerated\\VendingMachineInput.txt");
		run(gen);
	}
}

// For a basic sanity check:
class RandomInputGenerator implements Generator<Input> {
	public Input next() {
		return Input.randomSelection();
	}
}

// Create Inputs from a file of ';'-separated strings:
class FileInputGenerator implements Generator<Input> {
	private Iterator<String> input;

	public FileInputGenerator(String fileName) {
		input = new TextFile(fileName, ";").iterator();
	}

	public Input next() {
		if (!input.hasNext())
			return null;
		return Enum.valueOf(Input.class, input.next().trim());
	}
} /*
 * Output: 25 50 75 here is your CHIPS 0 100 200 here is your TOOTHPASTE 0 25 35
 * Your change: 35 0 25 35 Insufficient money for SODA 35 60 70 75 Insufficient
 * money for SODA 75 Your change: 75 0 Halted
 */// :~
